import { BaseMode } from "./interface";
import { Utils } from "./util";

let util = new Utils();
export class AdvTypeTest implements BaseMode{
    run(){
        
        //Intersection Types 交叉类型,合并类型
        //Person & Serializable & Loggable
        function extend<T, U>(first: T, second: U): T & U {
            let result = <T & U>{};
            for (let id in first) {
                (<any>result)[id] = (<any>first)[id];
            }
            util.log(second,"extend second");
            for (let id in second) {
                util.log(id,"extend second id");
                if (!result.hasOwnProperty(id)) {
                    (<any>result)[id] = (<any>second)[id];
                }
            }
            return result;
        }
        
        var jim = extend(new Person("Jim"), new ConsoleLogger());
        util.log(jim,"Intersection Types");
        var n = jim.name;
        //TODO: why not has log method??
        // jim.log();

        //union types 联合类型
        function isNumber(x: any): x is number {
            return typeof x === "number";
        }

        function isString(x: any): x is string {
            return typeof x === "string";
        }

        /**
         * Takes a string and adds "padding" to the left.
         * If 'padding' is a string, then 'padding' is appended to the left side.
         * If 'padding' is a number, then that number of spaces is added to the left side.
         */
        function padLeft(value: string, padding: number|string) {
          
            if (typeof padding === "number") {
                return Array(padding + 1).join(" ") + value;
            }
            if (typeof padding === "string") {
                return padding + value;
            }
             /*
            if (isNumber(padding)) {
                return Array(padding + 1).join(" ") + value;
            }
            if (isString(padding)) {
                return padding + value;
            }
             */
            throw new Error(`Expected string or number, got '${padding}'.`);
        }

        let res = padLeft("Hello world", 4); // returns "    Hello world"
        util.log(res,"padLeft string|number");


        function getSmallPet(pet:Fish | Bird): Fish | Bird {
            // ...
            // let pet ={} as Fish | Bird;
            return pet;
        }
        let fish = {} as Fish;
        fish.layEggs = function(){
            util.log("show fish layEggs","Fish");
        };
        fish.swim = function(){
             util.log("show fish swim","Fish");
        };
        let pet = getSmallPet(fish);        
        pet.layEggs(); // okay
        // pet.swim();    // errors
        //类型保护与区分类型
        if ((<Fish>pet).swim) {
            (<Fish>pet).swim();
        }
        else {
            (<Bird>pet).fly();
        }
        
        //用户自定义的类型保护
        //pet is Fish就是类型断言
        function isFish(pet: Fish | Bird): pet is Fish {
            return (<Fish>pet).swim !== undefined;
        }

        if (isFish(pet)){
            pet.swim();
        }else{
            pet.fly();
        }
       
       //类型别名      
       //Type Aliases
       type Container<T> = { value: T };
       type Tree<T> = {
            value: T;
            left: Tree<T>;
            right: Tree<T>;
        }

        //link
        type LinkedList<T> = T & { next: LinkedList<T> };
        interface Person {
            name: string;
        }
        var people: LinkedList<Person> = {name:"tanson"} as LinkedList<Person>;
        people.next = {name:"cichy"} as  LinkedList<Person>;
        util.log(people.name,"people.name");
        util.log(people.next.name,"people.next.name");

        //interface VS. alias
        //接口创建了一个新的名字，可以在其它任何地方使用
        //类型别名不能被extends和implements.
        interface ITree<T>{
            value: T;
            left: ITree<T>;
            right: ITree<T>;
        }

        //String Literal Types
        type Easing = "ease-in" | "ease-out" | "ease-in-out";
        class UIElement {
            animate(dx: number, dy: number, easing: Easing) {
                if (easing === "ease-in") {
                    // ...
                }
                else if (easing === "ease-out") {
                }
                else if (easing === "ease-in-out") {
                }
                else {
                    // error! should not pass null or undefined.
                }
            }
        }

        let button = new UIElement();
        button.animate(0, 0, "ease-in");
        // button.animate(0, 0, "uneasy"); // error: "uneasy" is not allowed here

        function createElement(tagName: "img"): HTMLImageElement;
        function createElement(tagName: "input"): HTMLInputElement;
        // ... more overloads ...
        function createElement(tagName: string): Element {
            // ... code goes here ...
            return {} as Element;
        }

        //可辨识联合（Discriminated Unions）

        //namespace join enum
        util.log(Color.mixColor("yellow"),"namespace join enum");

        //namespace join function
        util.log(buildLabel("Sam Smith"));

        /**
         *  具有普通的字符串字面量属性—可辨识的特征。
            一个类型别名包含了那些类型的联合—联合。
            此属性上的类型保护。
         */
        interface Square {
            kind: "square";
            size: number;
        }
        interface Rectangle {
            kind: "rectangle";
            width: number;
            height: number;
        }
        interface Circle {
            kind: "circle";
            radius: number;
        }

        type Shape = Square | Rectangle | Circle;
        let s:any = {width:10,height:20,size:20,kind: "rectangle"};
        //当没有涵盖所有可辨识联合的变化时，我们想让编译器可以通知我们
        //有两种方式可以实现。 
        //首先是启用 --strictNullChecks并且指定一个返回值类型
        //因为switch没有包涵所有情况，所以TypeScript认为这个函数有时候会返回undefined。
        // 如果你明确地指定了返回值类型为 number，那么你会看到一个错误，因为实际上返回值的类型为number | undefined。 
        // 然而，这种方法存在些微妙之处且 --strictNullChecks对旧代码支持不好。

        //完整性检查
        //第二种方法使用never类型，编译器用它来进行完整性检查：
        //assertNever检查s是否为never类型—即为除去所有可能情况后剩下的类型。
        // 如果你忘记了某个case，那么 s将具有一个真实的类型并且你会得到一个错误。
        // 这种方式需要你定义一个额外的函数，但是在你忘记某个case的时候也更加明显。
        function assertNever(x: never): never {
            throw new Error("Unexpected object: " + x);
        }
        function area(s: Shape):number {
            switch (s.kind) {
                case "square": return s.size * s.size;
                case "rectangle": return s.height * s.width;
                case "circle": return Math.PI * s.radius ** 2;
                default : return assertNever(s);
            }
        }
      

        util.log(area(s),"Discriminated Unions rectangle");
        s.kind = "square";
        util.log(area(s),"Discriminated Unions square");

        //多态的this类型
        //Polymorphic this types
        class BasicCalculator {
            public constructor(protected value: number = 0) { }
            public currentValue(): number {
                return this.value;
            }
            public add(operand: number): this {
                this.value += operand;
                return this;
            }
            public multiply(operand: number): this {
                this.value *= operand;
                return this;
            }
            // ... other operations go here ...
        }
        class ScientificCalculator extends BasicCalculator {
            public constructor(value = 0) {
                super(value);
            }
            public sin() {
                this.value = Math.sin(this.value);
                return this;
            }
            // ... other operations go here ...
        }
        // 如果没有this类型，ScientificCalculator就不能够在继承BasicCalculator的同时还保持接口的连贯性。
        // multiply将会返回BasicCalculator，它并没有sin方法。 
        // 然而，使用 this类型，multiply会返回this，在这里就是ScientificCalculator。
        let v = new ScientificCalculator(2)
                    .multiply(5)
                    .sin()
                    .add(1)
                    .currentValue();
        util.log(v,"ScientificCalculator this");  
    }
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void;
}
class ConsoleLogger implements Loggable {
    log():void {
        // ...
        util.log("ConsoleLogger");
    }
}

//Union Types
interface Bird {
    fly():void;
    layEggs():void;
}

interface Fish {
    swim():void;
    layEggs():void;
}

//namespace join enum
enum Color {
    red = 1,
    green = 2,
    blue = 4
}

namespace Color {
    export function mixColor(colorName: string) {
        if (colorName == "yellow") {
            return Color.red + Color.green;
        }
        else if (colorName == "white") {
            return Color.red + Color.green + Color.blue;
        }
        else if (colorName == "magenta") {
            return Color.red + Color.blue;
        }
        else if (colorName == "cyan") {
            return Color.green + Color.blue;
        }
    }
}

function buildLabel(name: string): string {
    return buildLabel.prefix + name + buildLabel.suffix;
}

namespace buildLabel {
    export let suffix = "";
    export let prefix = "Hello, ";
}
